#pragma once

#include "ProgramMain.hpp"
#include "ObjectCamera.hpp"
#include "FileChangeMonitor.hpp"

namespace gradients {
	class BaseGradient;
}

namespace IfsFractal {

class ValueMatrix;
struct InternalContext;

enum START_TYPE
{
	START_RANDOM,
	START_RANDOM_NORMAL,
	START_REGULAR,
	START_REGULAR_JITTERED,
	START_GRID,
	START_COUNT
};
extern const char * START_TYPE_NAMES[];

enum MATRIX_FORMAT
{
	MATRIX_FORMAT_DENSITY,
	MATRIX_FORMAT_COLOR_DENSITY,
	MATRIX_FORMAT_RGB_DENSITY,
	MATRIX_FORMAT_COUNT
};
extern const char * MATRIX_FORMAT_NAMES[];

enum GRADIENT_TYPE
{
	GRADIENT_NONE,
	GRADIENT_TEXTURE,
	GRADIENT_APOPHYSIS,
	GRADIENT_GIMP,
	GRADIENT_COUNT
};
extern const char * GRADIENT_TYPE_NAMES[];

struct BaseStartDesc
{
	COLORF ColorMin, ColorMax;

	BaseStartDesc();
	virtual ~BaseStartDesc() { }
	virtual void Load(const tokdoc::Node &node);
	virtual void Preprocess();

	void NewColor(COLORF &outColor) const;

private:
	// Preprocessed data
	bool m_ColorConst[4];
};

struct RandomStartDesc : public BaseStartDesc
{
	uint Count;
	RECTF Rect;

	RandomStartDesc();
	virtual void Load(const tokdoc::Node &node);
};

struct RandomNormalStartDesc : public BaseStartDesc
{
	uint Count;
	VEC2 Center, StdDev;

	RandomNormalStartDesc();
	virtual void Load(const tokdoc::Node &node);
};

struct RegularStartDesc : public BaseStartDesc
{
	POINT_ Count;
	RECTF Rect;
	bool Jittered;

	RegularStartDesc();
	virtual void Load(const tokdoc::Node &node);
};

struct GridStartDesc : public BaseStartDesc
{
	RECTF Rect;
	POINT_ LineCount; // Vertical lines, horizontal lines
	POINT_ PointsPerLine;

	GridStartDesc();
	virtual void Load(const tokdoc::Node &node);
};

BaseStartDesc * CreateStartDesc(START_TYPE type);


struct RenderDesc
{
	static const char * VALUE_FUNCTION_NAMES[];

	VEC2 PosScale, PosBias;
	RECTI Viewport;
	POINT_ VisibleIterationRange;
	float Gamma;
	bool LogColor;
	COLORF ColorScale, ColorBias;
	bool TransparentBackground;
	COLORF BackgroundColor; // Valid only when TransparentBackground == false.
	GRADIENT_TYPE GradientType;
	string GradientFilePath; // Valid only when GradientType == GRADIENT_TEXTURE, GRADIENT_APOPHYSIS.
	string GradientName; // Valid only when GradientType == GRADIENT_APOPHYSIS.

	RenderDesc();
	void Load(const tokdoc::Node &node, const POINT_ &textureSize);
	void Preprocess();

	bool IsGammaCorrection() const { return GammaCorrection; }
	bool IsColorBiasZero() const { return ColorBiasZero; }
	const RECTF & GetViewport_f() const { return m_Viewport_f; }

private:
	// Preprocessed data
	bool GammaCorrection;
	bool ColorBiasZero;
	RECTF m_Viewport_f;
};

struct TransformFunction
{
	enum TYPE
	{
		// Variations according to "The Fractal Flame Algorithm", Scott Draves, Erik Reckase
		TYPE_LINEAR = 0, // x, y
		TYPE_SINUSOIDAL, // sin(x), sin(y)
		TYPE_SPHERICAL, // 1/(r^2) * (x, y)
		TYPE_SWIRL, // (x * sin(r^2) - y * cos(r^2), x * cos(r^2) + y * sin(r^2))
		TYPE_HORSESHOE, // (1/r) * ((x-y) * (x+y), 2*x*y)
		TYPE_POLAR, // teta/pi, r-1
		TYPE_HANDKERCHIEF, // r * ( sin(teta+r), cos(teta-r) )
		TYPE_HEART, // r * ( sin(teta*r), -cos(teta*r) )
		TYPE_DISC, // teta/pi * ( sin(pi*r), cos(pi*r) )
		TYPE_SPIRAL, // 1/r * ( cos(teta)+sin(r), sin(teta)-cos(r) )
		TYPE_HYPERBOLIC, // sin(teta)/r, r*cos(teta)
		TYPE_DIAMOND, // sin(teta)*cos(r), cos(teta)*sin(r)
		TYPE_EX, // Uses Params[0..1].
		TYPE_JULIA, // sqrt(r) * ( cos(teta/2)+OMEGA, sin(teta/2+OMEGA) ). Uses random numbers.
		TYPE_BENT,
		TYPE_WAVES, // x + b*sin(y/(c^2)), y + e*sin(x/(f^2))
		TYPE_FISHEYE, // 2/(r+1) * (y, x)
		TYPE_POPCORN, // (x + c * sin(tan(3*y)), y + f*sin(tan(3*x)))
		TYPE_EXPONENTIAL, // exp(x-1) * ( cos(pi*y), sin(pi*y) )
		TYPE_POWER, // r^(sin(teta)) * ( cos(teta), sin(teta) )
		TYPE_COSINE, // cos(pi*x)*cosh(y), -sin(pi*x)*sinh(y)
		TYPE_RINGS, // ( (r+c^2) mod (2*c^2) - c^2 + r*(1-c^2) ) * ( cos(teta), sin(teta) )
		TYPE_FAN,
		TYPE_BLOB, // r*(p2 + (p1-p2)/2 * (sin(p3*teta) + 1)) * ( cos(teta), sin(teta) ). Uses Params[0..2].
		TYPE_PDJ, // (sin(p1*y) - cos(p2*x), sin(p3*x) - cos(p4*y)). Uses Params[0..3].
		TYPE_FAN2, // Uses Params[0..1].
		TYPE_RINGS2, // Uses Params[0].
		TYPE_EYEFISH,
		TYPE_BUBBLE,
		TYPE_CYLINDER,
		TYPE_PERSPECTIVE, // Uses Params[0..1].
		TYPE_NOISE, // Uses random numbers.
		TYPE_JULIA_N, // Uses Params[0..1] and random numbers.
		TYPE_JULIA_SCOPE, // Uses Params[0..1] and random numbers.
		TYPE_BLUR, // Uses random numbers.
		TYPE_GAUSSIAN, // Uses random numbers.
		//TYPE_RADIAL_BLUR // Uncertain about what's in the paper about this... - skipping.
		TYPE_PIE, // Uses Params[0..2] and random numbers.
		TYPE_NGON, // Uses Params[0..3] and random numbers.
		TYPE_CURL, // Uses Params[0..1].
		TYPE_RECTANGLES, // Uses Params[0..1].
		//TYPE_ARCH // Uncertain about what's in the paper about this... - skipping.
		TYPE_TANGENT,
		TYPE_SQUARE, // Uses random numbers.
		//TYPE_RAYS // Uncertain about what's in the paper about this... - skipping.
		//TYPE_BLADE // Uncertain about what's in the paper about this... - skipping.
		//TYPE_SECANT // Uncertain about what's in the paper about this... - skipping.
		//TYPE_TWINTRIAN // Uncertain about what's in the paper about this... - skipping.
		TYPE_CROSS,
		TYPE_COUNT
	};
	static const char * TYPE_NAMES[];

	uint Probability;
	bool ProcessColor;
	COLORF Color; // A is the density/value, default 1

	TYPE Type;
	AFFINE2D PreTransform;
	AFFINE2D PostTransform;
	float Params[4];

	TransformFunction();
	void Load(const tokdoc::Node &node);
	void TransformPoint(VEC2 &inoutPos, InternalContext &ctx);
	void Preprocess();

private:
	// Preprocessed data
	bool PreTransformIdentity, PostTransformIdentity;
};

enum TRANSFORM_MODE
{
	TRANSFORM_MODE_PROBABILITY,
	TRANSFORM_MODE_FORK,
	TRANSFORM_MODE_COUNT
};

extern const char * TRANSFORM_MODE_NAMES[];

struct Desc
{
	POINT_ TextureSize;
	scoped_ptr<BaseStartDesc> StartParams;
	RenderDesc RenderParams;
	uint IterationCount;
	TRANSFORM_MODE TransformMode;
	COLORF ColorWeight;
	std::vector< shared_ptr< TransformFunction > > Transforms;
	shared_ptr<TransformFunction> FinalTransform;
	MATRIX_FORMAT MatrixFormat;

	Desc();
	void Load(const tokdoc::Node &node);
	void Preprocess();

private:
	TransformFunction * LoadTransform(const tokdoc::Node &transformNode);
	void LoadStartParams(const tokdoc::Node &node);
};

class Screen : public IScreen, public IFileChangeEventListener
{
public:
	Screen();
	virtual ~Screen();
	
	virtual void Init();
	virtual void Update();
	virtual void Render3d();
	virtual void Render2d();
	virtual bool OnKeyDown(uint4 Key);
	virtual bool OnKeyUp(uint4 Key) { return false; }
	virtual bool OnChar(char Ch) { return false; }
	virtual bool OnMouseMove(const common::VEC2 &Pos);
	virtual bool OnMouseButton(const common::VEC2 &Pos, frame::MOUSE_BUTTON Button, frame::MOUSE_ACTION Action);
	virtual bool OnMouseWheel(const common::VEC2 &Pos, float Delta);
	virtual bool ProcessConsoleCommand(common::Tokenizer &tokenizer);

	virtual void OnFileChange(uint FileId);

private:
	static bool AskForSavePath(string &outFilePath);

	common::scoped_ptr<IDirect3DTexture9, common::ReleasePolicy> m_Texture;
	common::POINT_ m_TextureSize;
	ObjectCamera2d m_Camera;
	string m_ErrorMessage;
	string m_FilePath;

	void NewFractal();
	void MakeFractal(Desc &desc);

	void GenerateValueArray(ValueMatrix &matrix, const Desc &desc);
	void ProcessPoint(InternalContext &ctx, const Desc &desc, const VEC2 &pos, const COLORF &color);
	void ProcessPoint_Probability(InternalContext &ctx, const Desc &desc, const VEC2 &pos, const COLORF &color);
	void ProcessPoint_Fork(InternalContext &ctx, const Desc &desc, const VEC2 &pos, uint iterIndex, float iterIndex_f, const COLORF &color);
	void DrawPoint(InternalContext &ctx, const Desc &desc, const VEC2 &pos, const COLORF &color);
	void TransformPoint(VEC2 &inoutPos, COLORF &inoutColor, InternalContext &ctx, const Desc &desc);
	void TransformColor(COLORF &inoutColor, const TransformFunction &transform, const Desc &desc);
	
	void RenderValuesToTexture(IDirect3DTexture9 *texture, ValueMatrix &matrix, const Desc &desc, gradients::BaseGradient *gradient);
	float CalcToneMappingFactor(const Desc &desc, ValueMatrix &matrix);
	shared_ptr<gradients::BaseGradient> LoadGradient(const RenderDesc &renderDesc);

	void ResetView();
};

} // namespace IfsFractal
